/*-------------------------------------------------------------------------
 *
 * variable.c
 *        Routines for handling specialized SET variables.
 *
 *
 * Portions Copyright (c) 2012-2014, TransLattice, Inc.
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *      src/backend/commands/variable.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <ctype.h>

#include "access/htup_details.h"
#include "access/parallel.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/pg_authid.h"
#ifdef XCP
#include "catalog/pgxc_node.h"
#endif
#include "commands/variable.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/syscache.h"
#include "utils/snapmgr.h"
#include "utils/timestamp.h"
#include "utils/varlena.h"
#include "mb/pg_wchar.h"

/*
 * DATESTYLE
 */

/*
 * check_datestyle: GUC check_hook for datestyle
 */
bool
check_datestyle(char **newval, void **extra, GucSource source)
{// #lizard forgives
    int            newDateStyle = DateStyle;
    int            newDateOrder = DateOrder;
    bool        have_style = false;
    bool        have_order = false;
    bool        ok = true;
    char       *rawstring;
    int           *myextra;
    char       *result;
    List       *elemlist;
    ListCell   *l;

    /* Need a modifiable copy of string */
    rawstring = pstrdup(*newval);

    /* Parse string into list of identifiers */
    if (!SplitIdentifierString(rawstring, ',', &elemlist))
    {
        /* syntax error in list */
        GUC_check_errdetail("List syntax is invalid.");
        pfree(rawstring);
        list_free(elemlist);
        return false;
    }

    foreach(l, elemlist)
    {
        char       *tok = (char *) lfirst(l);

        /* Ugh. Somebody ought to write a table driven version -- mjl */

        if (pg_strcasecmp(tok, "ISO") == 0)
        {
            if (have_style && newDateStyle != USE_ISO_DATES)
                ok = false;        /* conflicting styles */
            newDateStyle = USE_ISO_DATES;
            have_style = true;
        }
        else if (pg_strcasecmp(tok, "SQL") == 0)
        {
            if (have_style && newDateStyle != USE_SQL_DATES)
                ok = false;        /* conflicting styles */
            newDateStyle = USE_SQL_DATES;
            have_style = true;
        }
        else if (pg_strncasecmp(tok, "POSTGRES", 8) == 0)
        {
            if (have_style && newDateStyle != USE_POSTGRES_DATES)
                ok = false;        /* conflicting styles */
            newDateStyle = USE_POSTGRES_DATES;
            have_style = true;
        }
        else if (pg_strcasecmp(tok, "GERMAN") == 0)
        {
            if (have_style && newDateStyle != USE_GERMAN_DATES)
                ok = false;        /* conflicting styles */
            newDateStyle = USE_GERMAN_DATES;
            have_style = true;
            /* GERMAN also sets DMY, unless explicitly overridden */
            if (!have_order)
                newDateOrder = DATEORDER_DMY;
        }
        else if (pg_strcasecmp(tok, "YMD") == 0)
        {
            if (have_order && newDateOrder != DATEORDER_YMD)
                ok = false;        /* conflicting orders */
            newDateOrder = DATEORDER_YMD;
            have_order = true;
        }
        else if (pg_strcasecmp(tok, "DMY") == 0 ||
                 pg_strncasecmp(tok, "EURO", 4) == 0)
        {
            if (have_order && newDateOrder != DATEORDER_DMY)
                ok = false;        /* conflicting orders */
            newDateOrder = DATEORDER_DMY;
            have_order = true;
        }
        else if (pg_strcasecmp(tok, "MDY") == 0 ||
                 pg_strcasecmp(tok, "US") == 0 ||
                 pg_strncasecmp(tok, "NONEURO", 7) == 0)
        {
            if (have_order && newDateOrder != DATEORDER_MDY)
                ok = false;        /* conflicting orders */
            newDateOrder = DATEORDER_MDY;
            have_order = true;
        }
        else if (pg_strcasecmp(tok, "DEFAULT") == 0)
        {
            /*
             * Easiest way to get the current DEFAULT state is to fetch the
             * DEFAULT string from guc.c and recursively parse it.
             *
             * We can't simply "return check_datestyle(...)" because we need
             * to handle constructs like "DEFAULT, ISO".
             */
            char       *subval;
            void       *subextra = NULL;

            subval = strdup(GetConfigOptionResetString("datestyle"));
            if (!subval)
            {
                ok = false;
                break;
            }
            if (!check_datestyle(&subval, &subextra, source))
            {
                free(subval);
                ok = false;
                break;
            }
            myextra = (int *) subextra;
            if (!have_style)
                newDateStyle = myextra[0];
            if (!have_order)
                newDateOrder = myextra[1];
            free(subval);
            free(subextra);
        }
        else
        {
            GUC_check_errdetail("Unrecognized key word: \"%s\".", tok);
            pfree(rawstring);
            list_free(elemlist);
            return false;
        }
    }

    pfree(rawstring);
    list_free(elemlist);

    if (!ok)
    {
        GUC_check_errdetail("Conflicting \"datestyle\" specifications.");
        return false;
    }

    /*
     * Prepare the canonical string to return.  GUC wants it malloc'd.
     */
    result = (char *) malloc(32);
    if (!result)
        return false;

    switch (newDateStyle)
    {
        case USE_ISO_DATES:
            strcpy(result, "ISO");
            break;
        case USE_SQL_DATES:
            strcpy(result, "SQL");
            break;
        case USE_GERMAN_DATES:
            strcpy(result, "German");
            break;
        default:
            strcpy(result, "Postgres");
            break;
    }
    switch (newDateOrder)
    {
        case DATEORDER_YMD:
            strcat(result, ", YMD");
            break;
        case DATEORDER_DMY:
            strcat(result, ", DMY");
            break;
        default:
            strcat(result, ", MDY");
            break;
    }

    free(*newval);
    *newval = result;

    /*
     * Set up the "extra" struct actually used by assign_datestyle.
     */
    myextra = (int *) malloc(2 * sizeof(int));
    if (!myextra)
        return false;
    myextra[0] = newDateStyle;
    myextra[1] = newDateOrder;
    *extra = (void *) myextra;

    return true;
}

/*
 * assign_datestyle: GUC assign_hook for datestyle
 */
void
assign_datestyle(const char *newval, void *extra)
{
    int           *myextra = (int *) extra;

    DateStyle = myextra[0];
    DateOrder = myextra[1];
}


/*
 * TIMEZONE
 */

/*
 * check_timezone: GUC check_hook for timezone
 */
bool
check_timezone(char **newval, void **extra, GucSource source)
{// #lizard forgives
    pg_tz       *new_tz;
    long        gmtoffset;
    char       *endptr;
    double        hours;

    if (pg_strncasecmp(*newval, "interval", 8) == 0)
    {
        /*
         * Support INTERVAL 'foo'.  This is for SQL spec compliance, not
         * because it has any actual real-world usefulness.
         */
        const char *valueptr = *newval;
        char       *val;
        Interval   *interval;

        valueptr += 8;
        while (isspace((unsigned char) *valueptr))
            valueptr++;
        if (*valueptr++ != '\'')
            return false;
        val = pstrdup(valueptr);
        /* Check and remove trailing quote */
        endptr = strchr(val, '\'');
        if (!endptr || endptr[1] != '\0')
        {
            pfree(val);
            return false;
        }
        *endptr = '\0';

        /*
         * Try to parse it.  XXX an invalid interval format will result in
         * ereport(ERROR), which is not desirable for GUC.  We did what we
         * could to guard against this in flatten_set_variable_args, but a
         * string coming in from postgresql.conf might contain anything.
         */
        interval = DatumGetIntervalP(DirectFunctionCall3(interval_in,
                                                         CStringGetDatum(val),
                                                         ObjectIdGetDatum(InvalidOid),
                                                         Int32GetDatum(-1)));

        pfree(val);
        if (interval->month != 0)
        {
            GUC_check_errdetail("Cannot specify months in time zone interval.");
            pfree(interval);
            return false;
        }
        if (interval->day != 0)
        {
            GUC_check_errdetail("Cannot specify days in time zone interval.");
            pfree(interval);
            return false;
        }

        /* Here we change from SQL to Unix sign convention */
        gmtoffset = -(interval->time / USECS_PER_SEC);
        new_tz = pg_tzset_offset(gmtoffset);

        pfree(interval);
    }
    else
    {
        /*
         * Try it as a numeric number of hours (possibly fractional).
         */
        hours = strtod(*newval, &endptr);
        if (endptr != *newval && *endptr == '\0')
        {
            /* Here we change from SQL to Unix sign convention */
            gmtoffset = -hours * SECS_PER_HOUR;
            new_tz = pg_tzset_offset(gmtoffset);
        }
        else
        {
            /*
             * Otherwise assume it is a timezone name, and try to load it.
             */
            new_tz = pg_tzset(*newval);

            if (!new_tz)
            {
                /* Doesn't seem to be any great value in errdetail here */
                return false;
            }

            if (!pg_tz_acceptable(new_tz))
            {
                GUC_check_errmsg("time zone \"%s\" appears to use leap seconds",
                                 *newval);
                GUC_check_errdetail("PostgreSQL does not support leap seconds.");
                return false;
            }
        }
    }

    /* Test for failure in pg_tzset_offset, which we assume is out-of-range */
    if (!new_tz)
    {
        GUC_check_errdetail("UTC timezone offset is out of range.");
        return false;
    }

    /*
     * Pass back data for assign_timezone to use
     */
    *extra = malloc(sizeof(pg_tz *));
    if (!*extra)
        return false;
    *((pg_tz **) *extra) = new_tz;

    return true;
}

/*
 * assign_timezone: GUC assign_hook for timezone
 */
void
assign_timezone(const char *newval, void *extra)
{
    session_timezone = *((pg_tz **) extra);
}

/*
 * show_timezone: GUC show_hook for timezone
 */
const char *
show_timezone(void)
{
    const char *tzn;

    /* Always show the zone's canonical name */
    tzn = pg_get_timezone_name(session_timezone);

    if (tzn != NULL)
        return tzn;

    return "unknown";
}


/*
 * LOG_TIMEZONE
 *
 * For log_timezone, we don't support the interval-based methods of setting a
 * zone, which are only there for SQL spec compliance not because they're
 * actually useful.
 */

/*
 * check_log_timezone: GUC check_hook for log_timezone
 */
bool
check_log_timezone(char **newval, void **extra, GucSource source)
{
    pg_tz       *new_tz;

    /*
     * Assume it is a timezone name, and try to load it.
     */
    new_tz = pg_tzset(*newval);

    if (!new_tz)
    {
        /* Doesn't seem to be any great value in errdetail here */
        return false;
    }

    if (!pg_tz_acceptable(new_tz))
    {
        GUC_check_errmsg("time zone \"%s\" appears to use leap seconds",
                         *newval);
        GUC_check_errdetail("PostgreSQL does not support leap seconds.");
        return false;
    }

    /*
     * Pass back data for assign_log_timezone to use
     */
    *extra = malloc(sizeof(pg_tz *));
    if (!*extra)
        return false;
    *((pg_tz **) *extra) = new_tz;

    return true;
}

/*
 * assign_log_timezone: GUC assign_hook for log_timezone
 */
void
assign_log_timezone(const char *newval, void *extra)
{
    log_timezone = *((pg_tz **) extra);
}

/*
 * show_log_timezone: GUC show_hook for log_timezone
 */
const char *
show_log_timezone(void)
{
    const char *tzn;

    /* Always show the zone's canonical name */
    tzn = pg_get_timezone_name(log_timezone);

    if (tzn != NULL)
        return tzn;

    return "unknown";
}


/*
 * SET TRANSACTION READ ONLY and SET TRANSACTION READ WRITE
 *
 * We allow idempotent changes (r/w -> r/w and r/o -> r/o) at any time, and
 * we also always allow changes from read-write to read-only.  However,
 * read-only may be changed to read-write only when in a top-level transaction
 * that has not yet taken an initial snapshot.  Can't do it in a hot standby,
 * either.
 *
 * If we are not in a transaction at all, just allow the change; it means
 * nothing since XactReadOnly will be reset by the next StartTransaction().
 * The IsTransactionState() test protects us against trying to check
 * RecoveryInProgress() in contexts where shared memory is not accessible.
 * (Similarly, if we're restoring state in a parallel worker, just allow
 * the change.)
 */
bool
check_transaction_read_only(bool *newval, void **extra, GucSource source)
{// #lizard forgives
    if (*newval == false && XactReadOnly && IsTransactionState() && !InitializingParallelWorker)
    {
        /* Can't go to r/w mode inside a r/o transaction */
        if (IsSubTransaction())
        {
            GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
            GUC_check_errmsg("cannot set transaction read-write mode inside a read-only transaction");
            return false;
        }
        /* Top level transaction can't change to r/w after first snapshot. */
        if (FirstSnapshotSet)
        {
            GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
            GUC_check_errmsg("transaction read-write mode must be set before any query");
            return false;
        }
        /* Can't go to r/w mode while recovery is still active */
        if (RecoveryInProgress())
        {
            GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED);
            GUC_check_errmsg("cannot set transaction read-write mode during recovery");
            return false;
        }
    }

    return true;
}

/*
 * SET TRANSACTION ISOLATION LEVEL
 *
 * We allow idempotent changes at any time, but otherwise this can only be
 * changed in a toplevel transaction that has not yet taken a snapshot.
 *
 * As in check_transaction_read_only, allow it if not inside a transaction.
 */
bool
check_XactIsoLevel(char **newval, void **extra, GucSource source)
{// #lizard forgives
    int            newXactIsoLevel;

    if (strcmp(*newval, "serializable") == 0)
    {
#ifdef PGXC
        /*
         * PGXCTODO - PGXC does not support 9.1 serializable transactions yet
         */
        newXactIsoLevel = XACT_REPEATABLE_READ;
#else
        newXactIsoLevel = XACT_SERIALIZABLE;
#endif
    }
    else if (strcmp(*newval, "repeatable read") == 0)
    {
        newXactIsoLevel = XACT_REPEATABLE_READ;
    }
    else if (strcmp(*newval, "read committed") == 0)
    {
        newXactIsoLevel = XACT_READ_COMMITTED;
    }
    else if (strcmp(*newval, "read uncommitted") == 0)
    {
        newXactIsoLevel = XACT_READ_UNCOMMITTED;
    }
    else if (strcmp(*newval, "default") == 0)
    {
        newXactIsoLevel = DefaultXactIsoLevel;
    }
    else
        return false;

    if (newXactIsoLevel != XactIsoLevel && IsTransactionState())
    {
        if (FirstSnapshotSet)
        {
            GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
            GUC_check_errmsg("SET TRANSACTION ISOLATION LEVEL must be called before any query");
            return false;
        }
        /* We ignore a subtransaction setting it to the existing value. */
        if (IsSubTransaction())
        {
            GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
            GUC_check_errmsg("SET TRANSACTION ISOLATION LEVEL must not be called in a subtransaction");
            return false;
        }
        /* Can't go to serializable mode while recovery is still active */
        if (newXactIsoLevel == XACT_SERIALIZABLE && RecoveryInProgress())
        {
            GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED);
            GUC_check_errmsg("cannot use serializable mode in a hot standby");
            GUC_check_errhint("You can use REPEATABLE READ instead.");
            return false;
        }
    }

    *extra = malloc(sizeof(int));
    if (!*extra)
        return false;
    *((int *) *extra) = newXactIsoLevel;

    return true;
}

void
assign_XactIsoLevel(const char *newval, void *extra)
{
    XactIsoLevel = *((int *) extra);
#ifdef PGXC
    /*
     * PGXCTODO - PGXC does not support 9.1 serializable transactions yet
     */
    if (XactIsoLevel == XACT_SERIALIZABLE)
        XactIsoLevel = XACT_REPEATABLE_READ;
#endif
}

const char *
show_XactIsoLevel(void)
{
    /* We need this because we don't want to show "default". */
    switch (XactIsoLevel)
    {
        case XACT_READ_UNCOMMITTED:
            return "read uncommitted";
        case XACT_READ_COMMITTED:
            return "read committed";
        case XACT_REPEATABLE_READ:
            return "repeatable read";
        case XACT_SERIALIZABLE:
            return "serializable";
        default:
            return "bogus";
    }
}

/*
 * SET TRANSACTION [NOT] DEFERRABLE
 */

bool
check_transaction_deferrable(bool *newval, void **extra, GucSource source)
{
    if (IsSubTransaction())
    {
        GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
        GUC_check_errmsg("SET TRANSACTION [NOT] DEFERRABLE cannot be called within a subtransaction");
        return false;
    }
    if (FirstSnapshotSet)
    {
        GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
        GUC_check_errmsg("SET TRANSACTION [NOT] DEFERRABLE must be called before any query");
        return false;
    }

    return true;
}

/*
 * Random number seed
 *
 * We can't roll back the random sequence on error, and we don't want
 * config file reloads to affect it, so we only want interactive SET SEED
 * commands to set it.  We use the "extra" storage to ensure that rollbacks
 * don't try to do the operation again.
 */

bool
check_random_seed(double *newval, void **extra, GucSource source)
{
    *extra = malloc(sizeof(int));
    if (!*extra)
        return false;
    /* Arm the assign only if source of value is an interactive SET */
    *((int *) *extra) = (source >= PGC_S_INTERACTIVE);

    return true;
}

void
assign_random_seed(double newval, void *extra)
{
    /* We'll do this at most once for any setting of the GUC variable */
    if (*((int *) extra))
        DirectFunctionCall1(setseed, Float8GetDatum(newval));
    *((int *) extra) = 0;
}

const char *
show_random_seed(void)
{
    return "unavailable";
}


/*
 * SET CLIENT_ENCODING
 */

bool
check_client_encoding(char **newval, void **extra, GucSource source)
{// #lizard forgives
    int            encoding;
    const char *canonical_name;

    /* Look up the encoding by name */
    encoding = pg_valid_client_encoding(*newval);
    if (encoding < 0)
        return false;

    /* Get the canonical name (no aliases, uniform case) */
    canonical_name = pg_encoding_to_char(encoding);

    /*
     * If we are not within a transaction then PrepareClientEncoding will not
     * be able to look up the necessary conversion procs.  If we are still
     * starting up, it will return "OK" anyway, and InitializeClientEncoding
     * will fix things once initialization is far enough along.  After
     * startup, we'll fail.  This would only happen if someone tries to change
     * client_encoding in postgresql.conf and then SIGHUP existing sessions.
     * It seems like a bad idea for client_encoding to change that way anyhow,
     * so we don't go out of our way to support it.
     *
     * Note: in the postmaster, or any other process that never calls
     * InitializeClientEncoding, PrepareClientEncoding will always succeed,
     * and so will SetClientEncoding; but they won't do anything, which is OK.
     */
    if (PrepareClientEncoding(encoding) < 0)
    {
        if (IsTransactionState())
        {
            /* Must be a genuine no-such-conversion problem */
            GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED);
            GUC_check_errdetail("Conversion between %s and %s is not supported.",
                                canonical_name,
                                GetDatabaseEncodingName());
        }
        else
        {
            /* Provide a useful complaint */
            GUC_check_errdetail("Cannot change \"client_encoding\" now.");
        }
        return false;
    }

    /*
     * Replace the user-supplied string with the encoding's canonical name.
     * This gets rid of aliases and case-folding variations.
     *
     * XXX Although canonicalizing seems like a good idea in the abstract, it
     * breaks pre-9.1 JDBC drivers, which expect that if they send "UNICODE"
     * as the client_encoding setting then it will read back the same way. As
     * a workaround, don't replace the string if it's "UNICODE".  Remove that
     * hack when pre-9.1 JDBC drivers are no longer in use.
     */
    if (strcmp(*newval, canonical_name) != 0 &&
        strcmp(*newval, "UNICODE") != 0)
    {
        free(*newval);
        *newval = strdup(canonical_name);
        if (!*newval)
            return false;
    }

    /*
     * Save the encoding's ID in *extra, for use by assign_client_encoding.
     */
    *extra = malloc(sizeof(int));
    if (!*extra)
        return false;
    *((int *) *extra) = encoding;

    return true;
}

void
assign_client_encoding(const char *newval, void *extra)
{
    int            encoding = *((int *) extra);

    /*
     * Parallel workers send data to the leader, not the client.  They always
     * send data using the database encoding.
     */
    if (IsParallelWorker())
    {
        /*
         * During parallel worker startup, we want to accept the leader's
         * client_encoding setting so that anyone who looks at the value in
         * the worker sees the same value that they would see in the leader.
         */
        if (InitializingParallelWorker)
            return;

        /*
         * A change other than during startup, for example due to a SET clause
         * attached to a function definition, should be rejected, as there is
         * nothing we can do inside the worker to make it take effect.
         */
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_TRANSACTION_STATE),
                 errmsg("cannot change client_encoding during a parallel operation")));
    }

    /* We do not expect an error if PrepareClientEncoding succeeded */
    if (SetClientEncoding(encoding) < 0)
        elog(LOG, "SetClientEncoding(%d) failed", encoding);
}


/*
 * SET SESSION AUTHORIZATION
 */

typedef struct
{
    /* This is the "extra" state for both SESSION AUTHORIZATION and ROLE */
    Oid            roleid;
    bool        is_superuser;
} role_auth_extra;

bool
check_session_authorization(char **newval, void **extra, GucSource source)
{
    HeapTuple    roleTup;
    Oid            roleid;
    bool        is_superuser;
    role_auth_extra *myextra;

    /* Do nothing for the boot_val default of NULL */
    if (*newval == NULL)
        return true;

    if (!IsTransactionState())
    {
        /*
         * Can't do catalog lookups, so fail.  The result of this is that
         * session_authorization cannot be set in postgresql.conf, which seems
         * like a good thing anyway, so we don't work hard to avoid it.
         */
        return false;
    }

    /* Look up the username */
    roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval));
    if (!HeapTupleIsValid(roleTup))
    {
        GUC_check_errmsg("role \"%s\" does not exist", *newval);
        return false;
    }

    roleid = HeapTupleGetOid(roleTup);
    is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;

    ReleaseSysCache(roleTup);

    /* Set up "extra" struct for assign_session_authorization to use */
    myextra = (role_auth_extra *) malloc(sizeof(role_auth_extra));
    if (!myextra)
        return false;
    myextra->roleid = roleid;
    myextra->is_superuser = is_superuser;
    *extra = (void *) myextra;

    return true;
}

void
assign_session_authorization(const char *newval, void *extra)
{
    role_auth_extra *myextra = (role_auth_extra *) extra;

    /* Do nothing for the boot_val default of NULL */
    if (!myextra)
        return;

    SetSessionAuthorization(myextra->roleid, myextra->is_superuser);
}


#ifdef XCP

/*
 * SET GLOBAL SESSION
 */

typedef struct
{
    /* This is the "extra" state for GLOBAL SESSION */
    Oid            coordid;
    int            coordpid;
} global_session_extra;


bool
check_global_session(char **newval, void **extra, GucSource source)
{// #lizard forgives
    HeapTuple    coordTup;
    Oid            coordid;
    char       *separatorPos;
    int         coordpid;
    global_session_extra *myextra;

    /* Do nothing for the boot_val default of NULL */
    if (*newval == NULL)
        return true;

    if (strcmp(*newval, "none") == 0)
    {
        /* hardwired translation */
        coordid = InvalidOid;
        coordpid = 0;
    }
    else
    {
        if (!IsTransactionState())
        {
            /*
             * Can't do catalog lookups, so fail.  The result of this is that
             * global_session cannot be set in postgresql.conf, which seems
             * like a good thing anyway, so we don't work hard to avoid it.
             */
            return false;
        }

        /*
         * Get pointer on '_' character separating coordinator name from pid in the
         * global session identifier
         */
        separatorPos = strrchr(*newval, '_');
        if (separatorPos == NULL)
        {
            GUC_check_errmsg("malformed Global Session identifier: \"%s\"", *newval);
            return false;
        }

        /*
         * The pid is written immediately after the separator
         */
        coordpid = atoi(separatorPos + 1);
        if (coordpid <= 0)
        {
            GUC_check_errmsg("malformed Global Session identifier: \"%s\"", *newval);
            return false;
        }


        /*
         * Temporary truncate the Global Session identifier to extract session name
         */
        *separatorPos = '\0';
        /* Look up the nodename */
        coordTup = SearchSysCache2(PGXCNODENAME, PointerGetDatum(*newval), PointerGetDatum(PGXCClusterName));
        if (!HeapTupleIsValid(coordTup))
        {
            *separatorPos = '_';
            GUC_check_errmsg("node \"%s\" does not exist", *newval);
            return false;
        }

        if (((Form_pgxc_node) GETSTRUCT(coordTup))->node_type != PGXC_NODE_COORDINATOR)
        {
            ReleaseSysCache(coordTup);
            *separatorPos = '_';
            GUC_check_errmsg("node \"%s\" is not a coordinator", *newval);
            return false;
        }

        coordid = HeapTupleGetOid(coordTup);

        /*
         * Save the coordinator name is a global variable so that we don't need
         * to do catalog lookups later, especially if we running outside a
         * transaction block or in an aborted transaction
         */
        if (OidIsValid(coordid))
            strncpy(MyCoordName, *newval, NAMEDATALEN);

        *separatorPos = '_';
        ReleaseSysCache(coordTup);
    }

    /* Set up "extra" struct for assign_session_authorization to use */
    myextra = (global_session_extra *) malloc(sizeof(global_session_extra));
    if (!myextra)
        return false;

    myextra->coordid = coordid;
    myextra->coordpid = coordpid;
    *extra = (void *) myextra;

    return true;
}


void
assign_global_session(const char *newval, void *extra)
{
    global_session_extra *myextra = (global_session_extra *) extra;

    /* Do nothing for the boot_val default of NULL */
    if (!myextra)
        return;

    SetGlobalSession(myextra->coordid, myextra->coordpid);
}
#endif


/*
 * SET ROLE
 *
 * The SQL spec requires "SET ROLE NONE" to unset the role, so we hardwire
 * a translation of "none" to InvalidOid.  Otherwise this is much like
 * SET SESSION AUTHORIZATION.
 */
extern char *role_string;        /* in guc.c */

bool
check_role(char **newval, void **extra, GucSource source)
{
    HeapTuple    roleTup;
    Oid            roleid;
    bool        is_superuser;
    role_auth_extra *myextra;

    if (strcmp(*newval, "none") == 0)
    {
        /* hardwired translation */
        roleid = InvalidOid;
        is_superuser = false;
    }
    else
    {
        if (!IsTransactionState())
        {
            /*
             * Can't do catalog lookups, so fail.  The result of this is that
             * role cannot be set in postgresql.conf, which seems like a good
             * thing anyway, so we don't work hard to avoid it.
             */
            return false;
        }

        /* Look up the username */
        roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval));
        if (!HeapTupleIsValid(roleTup))
        {
            GUC_check_errmsg("role \"%s\" does not exist", *newval);
            return false;
        }

        roleid = HeapTupleGetOid(roleTup);
        is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;

        ReleaseSysCache(roleTup);

        /*
         * Verify that session user is allowed to become this role, but skip
         * this in parallel mode, where we must blindly recreate the parallel
         * leader's state.
         */
        if (!InitializingParallelWorker &&
            !is_member_of_role(GetSessionUserId(), roleid))
        {
            GUC_check_errcode(ERRCODE_INSUFFICIENT_PRIVILEGE);
            GUC_check_errmsg("permission denied to set role \"%s\"",
                             *newval);
            return false;
        }
    }

    /* Set up "extra" struct for assign_role to use */
    myextra = (role_auth_extra *) malloc(sizeof(role_auth_extra));
    if (!myextra)
        return false;
    myextra->roleid = roleid;
    myextra->is_superuser = is_superuser;
    *extra = (void *) myextra;

    return true;
}

void
assign_role(const char *newval, void *extra)
{
    role_auth_extra *myextra = (role_auth_extra *) extra;

    SetCurrentRoleId(myextra->roleid, myextra->is_superuser);
}

const char *
show_role(void)
{
    /*
     * Check whether SET ROLE is active; if not return "none".  This is a
     * kluge to deal with the fact that SET SESSION AUTHORIZATION logically
     * resets SET ROLE to NONE, but we cannot set the GUC role variable from
     * assign_session_authorization (because we haven't got enough info to
     * call set_config_option).
     */
    if (!OidIsValid(GetCurrentRoleId()))
        return "none";

    /* Otherwise we can just use the GUC string */
    return role_string ? role_string : "none";
}
